﻿using Black.NBT;
using Microsoft.Win32;
using System;
using System.Linq;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace BlackNBT.ClientTest
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        #region Variables d'instances

        private bool _isModified;
        private NBTFile _currentFile;

        #endregion

        #region Constructeur

        /// <summary>
        /// Constructeur de la classe
        /// </summary>
        public MainWindow()
        {
            this.InitializeComponent();
        }

        #endregion

        #region Fonctions privées

        /// <summary>
        /// Ferme le fichier en cour
        /// </summary>
        private void CloseFile()
        {
            // Fermeture

            this._isModified = false;
            this._currentFile = null;

            this.MainTreeView.ItemsSource = null;
            this.MenuClose.IsEnabled = false;
            this.MenuSave.IsEnabled = false;
            this.MenuSaveAs.IsEnabled = false;

            this.RefreshTitle();
        }

        /// <summary>
        /// Crée un nouvel élément
        /// </summary>
        /// <param name="root">Racine de l'élément</param>
        /// <param name="toCreate">Type a créer</param>
        private void CreateTag(ITagHierarchicalData root, TagType toCreate)
        {
            Window edit;

            // Vérification

            if (root == null)
                return;

            // Création de la fenêtre d'édition

            switch (toCreate)
            {
                case (TagType.Byte):
                    edit = new EditByte();
                    break;

                case (TagType.ByteArray):
                    edit = new EditByteArray();
                    break;

                case (TagType.Compound):
                    edit = new EditCompound();
                    break;

                case (TagType.Double):
                    edit = new EditDouble();
                    break;

                case (TagType.Float):
                    edit = new EditFloat();
                    break;

                case (TagType.Int):
                    edit = new EditInt();
                    break;

                case (TagType.List):
                    edit = new EditList();
                    break;

                case (TagType.Long):
                    edit = new EditLong();
                    break;

                case (TagType.Short):
                    edit = new EditShort();
                    break;

                case (TagType.String):
                    edit = new EditString();
                    break;

                default:
                    return;
            }

            // Affichage et modification

            edit.Owner = this;

            if (edit.ShowDialog().Value)
            {
                root.Items.Add(((IDataEditWindows)edit).GetTag());
                this._isModified = true;
                this.RefreshTitle();
            }
        }

        /// <summary>
        /// Supprime in Tag récursivement
        /// </summary>
        /// <param name="instance">Instance a supprimer</param>
        /// <param name="collection">Collecion source</param>
        private bool DeleteTag(Tag instance, ITagHierarchicalData collection)
        {
            // Existance dans cette liste

            if (collection.Items.Contains(instance))
            {
                collection.Items.Remove(instance);
                return (true);
            }

            // Recherche des sous collections

            var result = from p in collection.Items
                         where p is ITagHierarchicalData
                         select p;

            foreach (ITagHierarchicalData data in result)
            {
                if (this.DeleteTag(instance, data))
                    return (true);
            }

            // Non supprimé

            return (false);
        }

        /// <summary>
        /// Edite un Tag
        /// </summary>
        /// <param name="tag">Instance du tag</param>
        private void EditTag(Tag tag)
        {
            Window edit;

            // Vérification

            if (tag == null)
                return;

            // Création de la fenêtre d'édition

            switch (tag.TagType)
            {
                case (TagType.Byte):
                    edit = new EditByte((TagByte)tag);
                    break;

                case (TagType.ByteArray):
                    edit = new EditByteArray((TagByteArray)tag);
                    break;

                case (TagType.Compound):
                    edit = new EditCompound((TagCompound)tag);
                    break;

                case (TagType.Double):
                    edit = new EditDouble((TagDouble)tag);
                    break;

                case (TagType.Float):
                    edit = new EditFloat((TagFloat)tag);
                    break;

                case (TagType.Int):
                    edit = new EditInt((TagInt)tag);
                    break;

                case (TagType.List):
                    edit = new EditList((TagList)tag);
                    break;

                case (TagType.Long):
                    edit = new EditLong((TagLong)tag);
                    break;

                case (TagType.Short):
                    edit = new EditShort((TagShort)tag);
                    break;

                case (TagType.String):
                    edit = new EditString((TagString)tag);
                    break;

                default:
                    return;
            }

            // Affichage et modification

            edit.Owner = this;

            if (edit.ShowDialog().Value)
            {
                this._isModified = true;
                this.RefreshTitle();
            }
        }

        /// <summary>
        /// Se produit sur la fermeture du client
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private void OnClosing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            if (!this.VerifyPendingChange())
                e.Cancel = true;
        }

        /// <summary>
        /// Se produit sur la suppression par le menu contextuel
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private void OnContextDeleteClick(object sender, RoutedEventArgs e)
        {
            // Récupération de l'item a supprimer

            Tag root = this.MainTreeView.SelectedItem as Tag;

            // Message d'avertissement

            if (MessageBox.Show("Are you sure to delete this element ?", "Delete", MessageBoxButton.YesNo, MessageBoxImage.Question) != MessageBoxResult.Yes)
                return;

            // Supression de l'élément

            if (this.DeleteTag(root, this._currentFile.Root))
            {
                this._isModified = true;
                this.RefreshTitle();
            }
        }

        /// <summary>
        /// Se produit sur l'édition par le menu contextuel
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private void OnContextEditClick(object sender, RoutedEventArgs e)
        {
            this.EditTag((Tag)(sender as FrameworkElement).DataContext);
        }

        /// <summary>
        /// Se produit sur la création par le menu contextuel
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private void OnContextNewClick(object sender, RoutedEventArgs e)
        {
            ITagHierarchicalData root = this.MainTreeView.SelectedItem as ITagHierarchicalData;

            if (root == null)
                return;

            switch (((MenuItem)sender).Header.ToString())
            {
                case ("TagByte"):
                    this.CreateTag(root, TagType.Byte);
                    break;

                case ("TagCompound"):
                    this.CreateTag(root, TagType.Compound);
                    break;

                case ("TagDouble"):
                    this.CreateTag(root, TagType.Double);
                    break;

                case ("TagFloat"):
                    this.CreateTag(root, TagType.Float);
                    break;

                case ("TagInt"):
                    this.CreateTag(root, TagType.Int);
                    break;

                case ("TagList"):
                    this.CreateTag(root, TagType.List);
                    break;

                case ("TagLong"):
                    this.CreateTag(root, TagType.Long);
                    break;

                case ("TagShort"):
                    this.CreateTag(root, TagType.Short);
                    break;

                case ("TagString"):
                    this.CreateTag(root, TagType.String);
                    break;
            }
        }

        /// <summary>
        /// Se produit sur l'ouverture du menu contextuel
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private void OnContextMenuOpened(object sender, RoutedEventArgs e)
        {
            Tag item = ((FrameworkElement)sender).DataContext as Tag;

            if (item == null)
                return;

            ContextMenu menu = (ContextMenu)sender;
            ((MenuItem)menu.Items[0]).IsEnabled = item.TagType == TagType.Compound || item.TagType == TagType.List;
            ((MenuItem)((MenuItem)menu.Items[0]).Items[0]).IsEnabled = item.TagType == TagType.Compound || (item.TagType == TagType.List && ((TagList)item).Type == TagType.Byte);
            ((MenuItem)((MenuItem)menu.Items[0]).Items[1]).IsEnabled = item.TagType == TagType.Compound || (item.TagType == TagType.List && ((TagList)item).Type == TagType.Compound);
            ((MenuItem)((MenuItem)menu.Items[0]).Items[2]).IsEnabled = item.TagType == TagType.Compound || (item.TagType == TagType.List && ((TagList)item).Type == TagType.Double);
            ((MenuItem)((MenuItem)menu.Items[0]).Items[3]).IsEnabled = item.TagType == TagType.Compound || (item.TagType == TagType.List && ((TagList)item).Type == TagType.Float);
            ((MenuItem)((MenuItem)menu.Items[0]).Items[4]).IsEnabled = item.TagType == TagType.Compound || (item.TagType == TagType.List && ((TagList)item).Type == TagType.Int);
            ((MenuItem)((MenuItem)menu.Items[0]).Items[5]).IsEnabled = item.TagType == TagType.Compound || (item.TagType == TagType.List && ((TagList)item).Type == TagType.List);
            ((MenuItem)((MenuItem)menu.Items[0]).Items[6]).IsEnabled = item.TagType == TagType.Compound || (item.TagType == TagType.List && ((TagList)item).Type == TagType.Long);
            ((MenuItem)((MenuItem)menu.Items[0]).Items[7]).IsEnabled = item.TagType == TagType.Compound || (item.TagType == TagType.List && ((TagList)item).Type == TagType.Short);
            ((MenuItem)((MenuItem)menu.Items[0]).Items[8]).IsEnabled = item.TagType == TagType.Compound || (item.TagType == TagType.List && ((TagList)item).Type == TagType.String);
        }

        /// <summary>
        /// Se produit sur le click du menu About
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private void OnMenuAboutClick(object sender, RoutedEventArgs e)
        {
            About about = new About();
            about.Owner = this;
            about.ShowDialog();
        }

        /// <summary>
        /// Se produit sur le click du menu Close
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private void OnMenuCloseClick(object sender, RoutedEventArgs e)
        {
            // Vérification enregistrement en cour

            if (!this.VerifyPendingChange())
                return;

            // Fermeture du fichier

            this.CloseFile();
        }

        /// <summary>
        /// Se produit sur le click du menu Exit
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private void OnMenuExitClick(object sender, RoutedEventArgs e)
        {
            if (this.VerifyPendingChange())
                this.Close();
        }

        /// <summary>
        /// Se produit sur le click du menu Open
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private void OnMenuOpenClick(object sender, RoutedEventArgs e)
        {
            // Vérification enregistrement en cour

            if (!this.VerifyPendingChange())
                return;

            // Ouverture de la dialog

            OpenFileDialog dlg = new OpenFileDialog();
            dlg.DefaultExt = ".dat";
            dlg.AddExtension = true;
            dlg.FilterIndex = 0;
            dlg.Filter = "NBT Data file (.dat)|*.dat|All files (*)|*.*";

            if (this._currentFile != null)
                dlg.FileName = this._currentFile.Filename;

            Nullable<bool> result = dlg.ShowDialog();

            if (!result.HasValue || !result.Value)
                return;

            // Ouverture du fichier

            try
            {
                this.CloseFile();
                this._currentFile = NBTFile.OpenFile(dlg.FileName);
                this.MainTreeView.ItemsSource = this._currentFile.Root.Items;
            }
            catch (Exception ex)
            {
                MessageBox.Show(string.Format("An error occured :\n\n{0}", ex.Message), "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }

            // On rafraichis le titre

            this.MenuClose.IsEnabled = true;
            this.MenuSave.IsEnabled = true;
            this.MenuSaveAs.IsEnabled = true;

            this.RefreshTitle();
        }

        /// <summary>
        /// Se produit sur le click du menu Save
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private void OnMenuSaveClick(object sender, RoutedEventArgs e)
        {
            if (this._currentFile != null && this.Save(this._currentFile.Filename))
                this.Title = string.Format("");

            this._isModified = false;
            this.RefreshTitle();
        }

        /// <summary>
        /// Se produit sur le click du menu SaveAs
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private void OnMenuSaveAsClick(object sender, RoutedEventArgs e)
        {
            // Vérification existance fichier

            if (this._currentFile == null)
                return;

            // Récupération du nomdde fichier

            SaveFileDialog dlg = new SaveFileDialog();
            dlg.FileName = this._currentFile.Filename;
            dlg.DefaultExt = ".dat";
            dlg.AddExtension = true;
            dlg.FilterIndex = 0;
            dlg.Filter = "NBT Data file (.dat)|*.dat|All files (*)|*.*";

            Nullable<bool> result = dlg.ShowDialog();

            if (!result.HasValue || !result.Value)
                return;

            // Sauvegarde

            this._currentFile.Filename = dlg.FileName;

            if (this.Save(this._currentFile.Filename))
                this.Title = string.Format("");

            this._isModified = false;
            this.RefreshTitle();
        }

        /// <summary>
        /// Se produit sur un click sur un noeud Double
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private void OnNodeMouseDown(object sender, MouseButtonEventArgs e)
        {
            // Est-ce un double click ?

            if (e.ClickCount == 2)
            {
                e.Handled = true;
                this.EditTag((Tag)(sender as FrameworkElement).DataContext);
            }
        }

        /// <summary>
        /// Rafraichis le titre de l'application
        /// </summary>
        private void RefreshTitle()
        {
            string title = "Black - NBT Client Test ";

            if (this._currentFile != null)
            {
                title += "[";

                if (this._isModified)
                    title += "*";

                title += this._currentFile.Filename + "]";
            }

            this.Title = title;
        }

        /// <summary>
        /// Sauvegarde le fichier a l'endroit indiqué
        /// </summary>
        /// <param name="filename">Chemin du fichier</param>
        /// <returns>TRUE sauvegarde réussie, FALSE sinon</returns>
        private bool Save(string filename)
        {
            try
            {
                // Sauvegarde

                this._currentFile.SaveAs(filename);

                // Terminée avec succès

                return (true);
            }
            catch (Exception e)
            {
                MessageBox.Show(string.Format("An error occured :\n\n{0}", e.Message), "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                return (false);
            }
        }

        /// <summary>
        /// Vérifie si des données sont en cours de modification
        /// </summary>
        /// <returns></returns>
        private bool VerifyPendingChange()
        {
            // On verifie si un fichier existe avec modication

            if (this._currentFile == null || !this._isModified)
                return (true);

            // Affichage du message

            MessageBoxResult result = MessageBox.Show("You have made ​​changes on this file, do you want to save ?", "Modification in progress", MessageBoxButton.YesNoCancel, MessageBoxImage.Warning);

            // Traitement résultat

            if (result == MessageBoxResult.Cancel)
                return (false);

            if (result == MessageBoxResult.Yes && !this.Save(this._currentFile.Filename))
                return (false);

            this._isModified = false;

            // Terminée

            return (true);
        }

        #endregion
    }
}
